#include "Composer.h"
#include <QFile>
#include <QDataStream>
#include <QStringBuilder>
#include "Composer/MessageComposer.h"
#include "Composer/SubjectMangling.h"
#include "Composer/Mailto.h"
#include "Composer/PlainTextFormatter.h"
#include "Composer/ReplaceSignature.h"
#include "app/Utils/Path.h"


namespace Dekko
{

namespace {
bool isReplyMessage(ReplyMode *replyMode) {
    return replyMode->replyMode() == ReplyMode::REPLY_ALL ||
            replyMode->replyMode() == ReplyMode::REPLY_ALL_BUT_ME ||
            replyMode->replyMode() == ReplyMode::REPLY_LIST ||
            replyMode->replyMode() == ReplyMode::REPLY_PRIVATE;
}

bool isForwardMessage(ReplyMode *replyMode) {
    return replyMode->replyMode() == ReplyMode::REPLY_FORWARD;
}

bool isNewMessage(ReplyMode *replyMode) {
    return replyMode->replyMode() == ReplyMode::REPLY_NONE;
}
}

MsgComposer::MsgComposer(QObject *parent) : QObject(parent),
    m_identities(0), m_recipients(0), m_manager(0), m_attachments(0) ,
    m_mboxSettings(0), m_submission(0),
    m_quoteOriginal(false), m_addressbook(0), m_composerOpen(false), m_fetchingPart(false),
    m_error(NO_ERROR)
{
    m_replyMode = new Dekko::ReplyMode(this);
    m_replyMessage = new Models::MessageModel(this);
    m_attachments = new Models::AttachmentListModel(this);
    m_mboxSettings = new Settings::MailboxSettings(this);
    m_submission = new SubmissionManager(this);
    resetAutoSaveLocation();
}

QObject *MsgComposer::selectedAccount() const
{
    if (m_manager->count() > 0) {
        return m_manager->getFromId(m_identities->getAccountIdForIdentityIdx(m_identities->selectedIndex()));
    }
    return 0;
}

QObject *MsgComposer::identities() const
{
    return m_identities;
}

QObject *MsgComposer::recipients() const
{
    return m_recipients;
}

QObject *MsgComposer::attachments() const
{
    return m_attachments;
}

QObject *MsgComposer::manager() const
{
    return m_manager;
}

void MsgComposer::setManager(QObject *manager)
{
    Accounts::AccountsManager *mgr = static_cast<Accounts::AccountsManager *>(manager);
    Q_ASSERT(mgr);
    if (!m_manager) {
        m_manager = mgr;
        connect(m_manager, &Accounts::AccountsManager::currentAccountChanged, this, &MsgComposer::updateIdentityIndex);
        connect(m_manager, &Accounts::AccountsManager::countChanged, this, &MsgComposer::updateIdentityModel, Qt::QueuedConnection);
        connect(m_manager, &Accounts::AccountsManager::accountsOpened, this, &MsgComposer::updateComposerSettings, Qt::QueuedConnection);
        emit managerChanged();
        m_recipients = new Recipients(this);
        connect(m_submission, SIGNAL(succeeded()), m_recipients, SLOT(clear()));
        Q_ASSERT(m_recipients);
        if (m_addressbook) {
            m_recipients->setContacts(m_addressbook);
        }
        m_identities = new Accounts::MultipleAccountSenderIdentityModel(this);
        connect(m_identities, &Accounts::MultipleAccountSenderIdentityModel::selectedIndexChanged, this, &MsgComposer::updateComposerSettings);
        connect(m_identities, &Accounts::MultipleAccountSenderIdentityModel::accountIdsChanged, this, &MsgComposer::updateIdentityIndex, Qt::QueuedConnection);
        Q_ASSERT(m_identities);
        m_recipients->setIdentityModel(m_identities);
        updateIdentityModel();
        emit modelsChanged();
    }
}

QObject *MsgComposer::mailboxSettings() const
{
    return m_mboxSettings;
}

QQuickTextDocument *MsgComposer::body() const
{
    return m_body;
}

void MsgComposer::setBody(QQuickTextDocument *body)
{
    m_body = body;
    emit bodyChanged();
}

int MsgComposer::replyMode() const
{
    return m_replyMode->replyMode();
}

void MsgComposer::setReplyMode(const int &mode)
{
    if (mode != m_replyMode->replyMode()) {
        qDebug() << "[MsgComposer]" << __func__ << "Setting reply mode";
        m_replyMode->setReplyMode((Dekko::ReplyMode::Mode)mode);
        updateComposerSettings();
        switch (m_replyMode->replyMode()) {
        case ReplyMode::REPLY_ALL:
        case ReplyMode::REPLY_ALL_BUT_ME:
        case ReplyMode::REPLY_LIST:
        case ReplyMode::REPLY_PRIVATE:
        case ReplyMode::REPLY_FORWARD:
        {
            qDebug() << "[MsgComposer]" << __func__ << "Preparing reply";
            prepareReply();
            break;
        }
        case ReplyMode::REPLY_NONE:
        {
            qDebug() << "[MsgComposer]" << __func__ << "ReplyNone... so reset composer";
            reset();
            break;
        }
        }
        emit replyModeChanged();
    }
}

QString MsgComposer::subject() const
{
    return m_subject;
}

void MsgComposer::setSubject(const QString &subject)
{
    if (m_subject != subject) {
        qDebug() << "[MsgComposer]" << __func__ << "Subject set";
        m_subject = subject;
        emit subjectChanged();
    }
}

QString MsgComposer::mailToUrl() const
{
    return m_mailToUrl;
}

void MsgComposer::setMailToUrl(const QString &url)
{
    if (m_mailToUrl != url) {
        qDebug() << "[MsgComposer]" << __func__ << "MAILTO URL SET: " << url;
        m_mailToUrl = url;
        emit mailToUrlChanged();
        //clear the composer and prepare to parse url
        qDebug() << "[MsgComposer]" << __func__ << "Clearing reply mode before parsing url";
        setReplyMode(ReplyMode::REPLY_NONE);
        qDebug() << "[MsgComposer]" << __func__ << "Parsing Url";
        parseMailToUrl();
    }
}

QModelIndex MsgComposer::replyPart() const
{
    return m_replyPart;
}

void MsgComposer::setReplyPart(const QModelIndex &part)
{
    if (m_replyPart != part) {
        qDebug() << "[MsgComposer]" << __func__ << "Reply part set: " << part;
        m_replyPart = part;
        emit replyPartChanged();
    }
}

QObject *MsgComposer::addressbook() const
{
    return m_addressbook;
}

QObject *MsgComposer::submission() const
{
    return m_submission;
}

bool MsgComposer::isFetchingPart() const
{
    return m_fetchingPart;
}

MsgComposer::Errors MsgComposer::error() const
{
    return m_error;
}

QString MsgComposer::errorString() const
{
    return m_errorString;
}

void MsgComposer::reset()
{
    qDebug() << "[MsgComposer]" << __func__;
    m_error = NO_ERROR;
    m_replyPart = QModelIndex();
    m_recipients->clear();
    m_attachments->clear();
    m_replyMessage->messageClosed();
    setSubject(QLatin1String(""));
    m_references.clear();
    m_inReplyTo.clear();
    m_body->textDocument()->clear();
    updateIdentityModel();
//    updateSignatureBlock();
}

void MsgComposer::prepareReply()
{
    if (isNewMessage(m_replyMode)) {
        qDebug() << "[MsgComposer]" << __func__ << "ReplyMode is REPLY_NONE, you shouldn't be calling prepare reply. Fix it!!";
        return;
    }
    QModelIndex msgIndex;
    // First check to see if we are working with a full mime message or part of a mime message.
    if (m_replyPart.isValid()) {
        qDebug() << "[MsgComposer]" << __func__ << "Setting message part to reply to";
        msgIndex = m_replyPart;
    } else {
        qDebug() << "[MsgComposer]" << __func__ << "Setting message to reply to";
        msgIndex = static_cast<Models::OneThreadMsgListModel *>(currentAccount()->singleThreadModel())->threadRootItem();
    }
    if (!msgIndex.isValid()) {
        qDebug() << "[MsgComposer]" << __func__ << "MsgIndex not valid";
        return;
    }
    // build recipient list
    m_recipients->buildRecipientList(m_replyMode->replyMode(), msgIndex);
    // setup message model for quoting
    m_replyMessage->messageClosed();
    disconnect(m_replyMessage, 0, this, 0);
    m_replyMessage->setImapModel(currentAccount()->imapModel());
    m_replyMessage->setMailbox(static_cast<Imap::Mailbox::MsgListModel *>(currentAccount()->msgListModel())->mailboxName());
    m_replyMessage->set_accountId(currentAccount()->accountId());
    // Just use the plaintext part until we support multipart alternative messages
    m_replyMessage->set_preferPlainText(true);
    m_replyMessage->setMessageModelIndex(msgIndex);
    connect(m_replyMessage, &Models::MessageModel::partNowAvailable, this, &MsgComposer::setMessageBody);
    if (isForwardMessage(m_replyMode)) {
        qDebug() << "[MsgComposer]" << __func__ << "Is forward message, setting message/part as an attachment";
        setSubject(Composer::Util::forwardSubject(m_replyMessage->subject()));
        if (m_replyPart.isValid()) {
            qDebug() << "[MsgComposer]" << __func__ << "Forward Part is valid: adding imap part attachment";
            m_attachments->addImapPartAttachment(msgIndex, static_cast<QAbstractItemModel *>(currentAccount()->imapModel()));
        } else {
            qDebug() << "[MsgComposer]" << __func__ << "Forward message is valid: adding imap message attachment";
            m_attachments->addImapMessageAttachment(msgIndex, static_cast<QAbstractItemModel *>(currentAccount()->imapModel()));
        }
        if (!m_body->textDocument()->isEmpty()) {
            qDebug() << "[MsgComposer]" << __func__ << "Message body not empty, but should be for a FORWARD: clearing document";
            m_body->textDocument()->clear();
        }
        return; // Return now as we don't need the message body
    } else {
        qDebug() << "[MsgComposer]" << __func__ << "Setting subject for reply";
        setSubject(Composer::Util::replySubject(m_replyMessage->subject()));
        m_inReplyTo = m_replyMessage->messageId();
        m_references = m_replyMessage->references();
    }
    qDebug() << "[MsgComposer]" << __func__ << "ReplyPart is HTML: " << m_replyMessage->mainPartIsHtml();
    qDebug() << "[MsgComposer]" << __func__ << "Should quote original part: " << m_quoteOriginal;

    if (!m_replyMessage->mainPartIsHtml() && m_quoteOriginal) {
        qDebug() << "[MsgComposer]" << __func__ << "Setting message body for reply";
        setMessageBody();
    }
}

void MsgComposer::send()
{
    // Check we have what we need before signalling submission
    // has started.
    if (!m_recipients->hasValidToDestination()) {
        setError(NO_VALID_TO_RECIPIENTS);
        qDebug() << "[MsgComposer]" << __func__ << "No valid recipients... abort!";
        return;
    }
    if (m_subject.isEmpty()) {
        setError(NO_VALID_SUBJECT);
        qDebug() << "[MsgComposer]" << __func__ << "No valid subject... abort!";
        return;
    }
    if (m_identities->rowCount() <= 0) {
        setError(NO_IDENTITIES);
        qDebug() << "[MsgComposer]" << __func__ << "No identities configured... abort!";
        return;
    }
    if (m_identities->selectedIndex() == -1) {
        setError(NO_IDENTITY_SELECTED);
        qDebug() << "[MsgComposer]" << __func__ << "No identity selected... abort!";
        return;
    }
    emit submissionStarting();

    saveDraft(m_inProgressDraft);
    updateSignatureBlock();

    m_submission->setCurrentAccount(currentAccount());
    m_submission->setAccount(selectedAccount());

    switch (m_replyMode->replyMode()) {
    case ReplyMode::REPLY_ALL:
    case ReplyMode::REPLY_ALL_BUT_ME:
    case ReplyMode::REPLY_LIST:
    case ReplyMode::REPLY_PRIVATE:
        qDebug() << "[MsgComposer]" << __func__ << "Setting reply references";
        m_submission->setInReplyTo(m_inReplyTo);
        m_submission->setReferences(m_references);
        m_submission->setReplyingToMessage(m_replyMessage->messageIndex());
        break;
    case ReplyMode::REPLY_FORWARD:
    case ReplyMode::REPLY_NONE:
        break;
    }
    if (m_submission->setupConnection()) {
        qDebug() << "[MsgComposer]" << __func__ << "Connection created";
        m_submission->setRecipientList(m_recipients->toRecipientList());
        Accounts::SenderIdentity *identity = m_identities->getIdentity(m_identities->selectedIndex());
        if (m_attachments->rowCount() > 0) {
            qDebug() << "[MsgComposer]" << __func__ << "Adding attachments for submission";
            for (int i = 0; i < m_attachments->rowCount(); i++) {
                m_submission->composer()->addAttachmentItem(m_attachments->getAttachmentAtIndex(i));
            }
        }
        if (m_submission->buildMessage(identity->toFormattedString(), m_subject, m_body->textDocument()->toPlainText())) {
            qDebug() << "[MsgComposer]" << __func__ << "Message built successfully. Now sending message";
            QVariantMap keys = m_mboxSettings->keys();
            m_submission->send(m_mboxSettings->read(keys.value(QStringLiteral("saveToImap")).toString()).toBool(),
                               m_mboxSettings->read(keys.value(QStringLiteral("sentMailbox")).toString()).toString());
        }
    }
}

void MsgComposer::saveDraft(const QString &path)
{
    static const int draftVersion = 1;
    QFile file(path);
    if (!file.open(QIODevice::WriteOnly)) {
        return;
    }
    QDataStream stream(&file);
    stream.setVersion(QDataStream::Qt_5_4);
    // First add our metadata.
    stream << draftVersion;
    // ReplyMode
    stream << (int)m_replyMode->replyMode();
    // Selected accountId
    stream << m_identities->getAccountIdForIdentityIdx(m_identities->selectedIndex());
    // SelectedIdentity
    Accounts::SenderIdentity *identity = m_identities->getIdentity(m_identities->selectedIndex());
    stream << identity->name() << identity->email();
    // Recipients
    QQmlObjectListModel<Recipient> *recipients = static_cast<QQmlObjectListModel<Recipient> *>(m_recipients->model());
    Q_ASSERT(recipients);
    // Save count
    stream << recipients->count();
    // save recipient type & pretty address.
    foreach (Recipient *recipient, recipients->toList()) {
        stream << (int)recipient->type();
        stream << recipient->mailAddress().asPrettyString();
    }
    // timestamp
    stream << QDateTime::currentDateTime();
    // InReplyTo & References
    stream << m_inReplyTo << m_references;
    // Subject
    stream << m_subject;
    // body
    stream << m_body->textDocument()->toPlainText();
    // TODO: attachments
    file.close();
    file.setPermissions(QFile::ReadOwner|QFile::WriteOwner);
}

void MsgComposer::loadDraft(const QString &path)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        return;
    }
    if (path != m_inProgressDraft) {
        QFile::remove(m_inProgressDraft);
        m_inProgressDraft = path;
    }

    int draftVersion;
    QString accountId;
    QString identityName;
    QString identityEmail;
    int recipientCount;

    QDataStream stream(&file);
    stream.setVersion(QDataStream::Qt_5_4);
    stream >> draftVersion;
    int mode;
    stream >> mode;
    m_replyMode->setReplyMode(static_cast<Dekko::ReplyMode::Mode>(mode));

    stream >> accountId;
    stream >> identityName >> identityEmail;
    stream >> recipientCount;

    for (int i = 0; i < recipientCount; ++i) {
        int type;
        QString address;
        stream >> type >> address;
        m_recipients->addRecipient((Recipient::Type)type, address);
    }

    QDateTime timestamp;
    stream >> timestamp;
    stream >> m_inReplyTo >> m_references;
    stream >> m_subject;
    QString body;
    stream >> body;
    m_body->textDocument()->setPlainText(body);
    file.close();
}

void MsgComposer::setAddressBook(QObject *addressbook)
{
    if (m_addressbook == addressbook)
        return;

    m_addressbook = addressbook;
    if (m_recipients) {
        m_recipients->setContacts(m_addressbook);
    }
    qDebug() << __FILE__ << __func__ << "Addressbook set";
    emit addressbookChanged(addressbook);
}

void MsgComposer::updateSignatureBlock()
{
    qDebug() << "[MsgComposer]" << __func__;
    Accounts::SenderIdentity *identity = m_identities->getIdentity(m_identities->selectedIndex());
    Q_ASSERT(identity);
    Composer::Util::replaceSignature(m_body->textDocument(), identity->signature());
}

void MsgComposer::appendTextToBody(const QString &text)
{
    if (!text.isEmpty()) {
        m_body->textDocument()->setPlainText(text);
    }
}

void MsgComposer::updateComposerSettings()
{
    qDebug() << "[MsgComposer]" << __func__;
    m_mboxSettings->setAccount(selectedAccount());
    QVariantMap keys = m_mboxSettings->keys();
    m_quoteOriginal = m_mboxSettings->read(keys.value(QStringLiteral("quoteOriginalMessage")).toString()).toBool();
    m_replyPosition = m_mboxSettings->read(keys.value(QStringLiteral("replyQuotePosition")).toString()).toString();
    if (m_quoteOriginal && isReplyMessage(m_replyMode)) {
        if (m_body->textDocument()->isEmpty()) {
            setMessageBody();
        }
    } else {
        qDebug() << "[MsgComposer]" << __func__ << "Clearing message body";
        m_body->textDocument()->clear();
    }
    bool includeCC = m_mboxSettings->read(keys.value(QStringLiteral("includeCC")).toString()).toBool();
    bool includeBCC = m_mboxSettings->read(keys.value(QStringLiteral("includeBCC")).toString()).toBool();
    if (includeCC) {
        QStringList includes = m_mboxSettings->read(keys.value(QStringLiteral("cc")).toString()).toString().split(',');
        for (int i = 0; i < includes.size() - 1; ++i) {
            includes.replace(i, includes.at(i).trimmed());
        }
        qDebug() << "[MsgComposer]" << __func__ << "Setting CC includes: " << includes;
        m_recipients->setIncludes(includes, true);
    }else {
        qDebug() << "[MsgComposer]" << __func__ << "Clearing CC includes";
        m_recipients->clearIncludes(true);
    }
    if (includeBCC) {
        QStringList includes = m_mboxSettings->read(keys.value(QStringLiteral("bcc")).toString()).toString().split(',');
        for (int i = 0; i < includes.size() - 1; ++i) {
            includes.replace(i, includes.at(i).trimmed());
        }
        qDebug() << "[MsgComposer]" << __func__ << "Setting BCC includes: " << includes;
        m_recipients->setIncludes(includes, false);
    } else {
        qDebug() << "[MsgComposer]" << __func__ << "Clearing BCC includes";
        m_recipients->clearIncludes(false);
    }
    emit identityChanged();
    qDebug() << "[MsgComposer]" << __func__ << "finished";
}

void MsgComposer::parseMailToUrl()
{
    QString decodedSubject;
    QString decodedBody;
    QList<QPair<Composer::RecipientKind, QString>> decodedRecipients;
    QList<QByteArray> decodedInReplyTo;
    QList<QByteArray> decodedReferences;

    Composer::parseRFC6068Mailto(QUrl(m_mailToUrl), decodedSubject, decodedBody, decodedRecipients, decodedInReplyTo, decodedReferences);
    for (int i = 0; i < decodedRecipients.size(); i++) {
        m_recipients->addRecipient((Recipient::Type)decodedRecipients[i].first, decodedRecipients[i].second);
    };

    if (decodedInReplyTo.size() > 0)
        m_inReplyTo = decodedInReplyTo[0];
    m_references = decodedReferences;
    setSubject(decodedSubject);
    m_body->textDocument()->setPlainText(decodedBody);
}

Accounts::Account *MsgComposer::currentAccount()
{
    return static_cast<Accounts::Account *>(m_manager->currentAccount());
}

QString MsgComposer::quoteReplyMessage(const QString &message)
{
    QString sender;
    // TODO: add option in settings for user to configure how this should look
    QQmlObjectListModel<Models::MessageContact> *fromModel = static_cast<QQmlObjectListModel<Models::MessageContact> *>(m_replyMessage->from());
    Models::MessageContact *from = fromModel->first();
    if (!from->displayName().isEmpty()) {
        sender = from->displayName();
    } else {
        sender = tr("you");
    }
    QString replyPart =  tr("On %1, %2 wrote:\n").arg(m_replyMessage->date().toLocalTime().toString(Qt::SystemLocaleLongDate), sender);
    QString quotedPart = replyPart + QStringList(Composer::Util::quoteText(message.split(QStringLiteral("\n")))).join(QStringLiteral("\n"));
    if (m_replyPosition == QStringLiteral("before")) {
        quotedPart.prepend(QStringLiteral("\n\n"));
    } else if (m_replyPosition == QStringLiteral("after")) {
        quotedPart.append(QStringLiteral("\n\n"));
    } else {
        // Do nothing
    }
    return quotedPart;
}

void MsgComposer::resetAutoSaveLocation()
{
    m_inProgressDraft = (Dekko::cacheLocation() % "/SMTP/Drafts/autosave/");
    QDir().mkpath(m_inProgressDraft);
    m_inProgressDraft += QStringLiteral("autosave.draft");
}

void MsgComposer::updateIdentityIndex()
{
//    updateIdentityModel();
    qDebug() << "[MsgComposer]" << __func__;
    if (m_manager->getSmtpCapableAccountIds().contains(currentAccount()->accountId())) {
        qDebug() << "[MsgComposer]" << __func__ << "Getting first identity index for: " << currentAccount()->accountId();
        m_identities->setSelectedIndex(m_identities->getFirstIdentityIdxForAccountId(currentAccount()->accountId()));
    } else {
        qDebug() << "[MsgComposer]" << __func__ << "No identities configured for current account. Selecting first usable identity";
        m_identities->setSelectedIndex(0);
    }
    //    updateSignatureBlock();
}

void MsgComposer::updateIdentityModel()
{
    m_identities->setAccountIds(m_manager->getSmtpCapableAccountIds());
    updateIdentityIndex();
}

void MsgComposer::setMessageBody()
{
    m_fetchingPart = false;
    QString textPart = m_replyMessage->mainTextPartIndex().data(Imap::Mailbox::RolePartUnicodeText).toString();
    m_body->textDocument()->clear();
    if (!textPart.isEmpty()) {
        qDebug() << "[MsgComposer]" << __func__ << "Message part available locally. Setting reply body part";
        m_body->textDocument()->setPlainText(quoteReplyMessage(textPart));
    } else {
        // it still needs fetching so do that now
        qDebug() << "[MsgComposer]" << __func__ << "Message part not available locally. Fetching part now...";
        m_fetchingPart = true;
        m_replyMessage->fetchPart(m_replyMessage->messageIndex(), m_replyMessage->mainTextPartIndex());
    }
}

void MsgComposer::setError(const MsgComposer::Errors &error)
{
    m_error = error;
    switch (m_error) {
    case NO_ERROR:
        // This is a good thing... honest!
        m_errorString.clear();
        break;
    case NO_VALID_TO_RECIPIENTS:
        m_errorString = tr("This message has no destination");
        break;
    case NO_IDENTITIES:
        m_errorString = tr("No configured identities. Have you got an smtp account setup?");
        break;
    case NO_IDENTITY_SELECTED:
        m_errorString = tr("Please select an identity for this message");
        break;
    case NO_VALID_SUBJECT:
        m_errorString = tr("Subject required");
        break;
    }
    emit hasError();
}
}

